use godot::{
    classes::{ColorRect, Control, IColorRect, notify::ControlNotification},
    prelude::*,
};

#[derive(GodotClass)]
#[class(init, base=Control)]
pub struct OverlayView {
    base: Base<Control>,
}

#[godot_api]
impl OverlayView {
    #[allow(unused)]
    #[func(gd_self, virtual)]
    pub fn on_pause(this: Gd<Self>) {}

    #[allow(unused)]
    #[func(gd_self, virtual)]
    pub fn on_resume(this: Gd<Self>) {}

    #[func]
    pub fn get_manager(&self) -> Option<Gd<OverlayManager>> {
        self.base()
            .get_parent()
            .and_then(|p| p.try_cast::<OverlayManager>().ok())
    }

    #[func]
    pub fn jump_to(&self, other: Gd<OverlayView>) {
        if let Some(mut manager) = self.get_manager() {
            manager.bind_mut().push_view(other);
        }
    }

    #[func]
    pub fn close(&self) {
        if let Some(mut manager) = self.get_manager() {
            manager.call_deferred(&StringName::from(c"free_top"), &[]);
        }
    }
}

#[derive(GodotClass)]
#[class(init, base=ColorRect)]
pub struct OverlayManager {
    base: Base<ColorRect>,
}

#[godot_api]
impl IColorRect for OverlayManager {
    fn on_notification(&mut self, what: ControlNotification) {
        match what {
            ControlNotification::APPLICATION_FOCUS_OUT
            | ControlNotification::APPLICATION_PAUSED => {
                if let Some(top) = self.get_view(-1) {
                    OverlayView::on_pause(top);
                }
            }
            ControlNotification::APPLICATION_FOCUS_IN
            | ControlNotification::APPLICATION_RESUMED => {
                if let Some(top) = self.get_view(-1) {
                    OverlayView::on_resume(top);
                }
            }
            _ => {}
        }
    }
}

#[godot_api]
impl OverlayManager {
    #[func]
    pub fn get_view(&self, index: i32) -> Option<Gd<OverlayView>> {
		// to avoid godot error.
        if self.base().get_child_count() == 0 {
            return None;
        }

        self.base()
            .get_child(index)
            .and_then(|c| c.try_cast::<OverlayView>().ok())
    }

    #[func]
    pub fn push_view(&mut self, view: Gd<OverlayView>) {
        self.base_mut().show();

        if let Some(mut child) = self.get_view(-1) {
            OverlayView::on_pause(child.clone());
            child.hide();
        }

        self.base_mut().add_child(&view);
    }

    #[func]
    pub fn replace_view(&mut self, view: Gd<OverlayView>) {
        self.base_mut().show();

        if self.base().get_child_count() > 0 {
			// to avoid godot error.
            let mut top = self.base().get_child(-1).unwrap();
            if let Ok(view) = top.clone().try_cast::<OverlayView>() {
                OverlayView::on_pause(view.clone());
            }
            top.queue_free();
        }

        self.base_mut().add_child(&view);
    }

    #[func]
    pub fn pop_view(&mut self) -> Option<Gd<OverlayView>> {
        let mut base = self.base_mut();
        // to avoid godot error.
        if base.get_child_count() == 0 {
            return None;
        }

        let ret = base.get_child(-1).and_then(|c| {
            base.remove_child(&c);
            c.try_cast::<OverlayView>().ok()
        })?;

        OverlayView::on_pause(ret.clone());

        if base.get_child_count() > 0
            && let Ok(mut top) = base.get_child(-1).unwrap().try_cast::<OverlayView>()
        {
            top.show();
            OverlayView::on_resume(top);
        } else {
            base.hide();
        }

        Some(ret)
    }

    #[func]
    pub fn free_top(&mut self) {
        if let Some(mut view) = self.pop_view() {
            view.queue_free();
        }
    }

    #[func]
    pub fn clear(&mut self) {
		let base = self.base();
        while base.get_child_count() > 0 {
			let mut child = base.get_child(-1).unwrap();
            if let Ok(view) = child.clone().try_cast::<OverlayView>() {
                OverlayView::on_pause(view.clone());
            }
            child.queue_free();
        }
    }

    #[func]
    pub fn close(&mut self) {
        self.clear();
        self.base_mut().hide();
    }
}
